﻿using PMS.Core.Caching;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Core.Infrastructure;
using PMS.Data.Entities.ScheduleTasks;
using PMS.Services.EntityServices.Logging;
using PMS.Services.EntityServices.SchduleTasks;
using PMS.Services.SharedKernel;
using System;
using System.Linq;

namespace PMS.Services.Utils.ScheduleTasks
{
    /// <summary>
    /// Task
    /// </summary>
    public partial class ProcessTask
    {
        #region Fields

        private bool? _enabled;

        #endregion

        #region Ctor

        /// <summary>
        /// Ctor for Task
        /// </summary>
        /// <param name="task">Task </param>
        public ProcessTask(SystemScheduleTask taskentity)
        {
            ScheduleTask = taskentity;
        }

        #endregion

        #region Utilities

        /// <summary>
        /// Initialize and execute task
        /// </summary>
        private void ExecuteTask()
        {
            var scheduleTaskService = EngineContext.Current.Resolve<IScheduleTaskService>();

            if (!Enabled)
                return;

            var type = Type.GetType(ScheduleTask.Type) ??
                //ensure that it works fine when only the type name is specified (do not require fully qualified names)
                AppDomain.CurrentDomain.GetAssemblies()
                .Select(a => a.GetType(ScheduleTask.Type))
                .FirstOrDefault(t => t != null);
            if (type == null)
                throw new Exception($"Schedule task ({ScheduleTask.Type}) cannot by instantiated");

            object instance = null;
            instance = EngineContext.Current.Resolve(type);
            if (instance == null)
            {
                //not resolved
                instance = EngineContext.Current.ResolveUnregistered(type);
            }

            var task = instance as IScheduleTask;
            if (task == null)
                return;

            ScheduleTask.LastStartTimeUtc = DateTime.UtcNow;
            //update appropriate datetime properties
            scheduleTaskService.UpdateTask(ScheduleTask);
            bool isOk = task.Execute();
            if (isOk)
            {
                ScheduleTask.LastStopTimeUtc = ScheduleTask.LastSuccessTimeUtc = DateTime.UtcNow;
                ScheduleTask.Status = ScheduleJobActionStatus.Complete;
            }
            else
            {
                ScheduleTask.LastStopTimeUtc = DateTime.UtcNow;
                ScheduleTask.Status = ScheduleJobActionStatus.Error;
            }
            //update appropriate datetime properties
            scheduleTaskService.UpdateTask(ScheduleTask);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Executes the task
        /// </summary>
        /// <param name="throwException">A value indicating whether exception should be thrown if some error happens</param>
        public void Execute(bool throwException = false)
        {
            if (ScheduleTask == null || !Enabled)
                return;

            try
            {
                //get expiration time
                int expirationInSeconds = Math.Min(ScheduleTask.RunTimeOut.HasValue 
                                                    ? ScheduleTask.RunTimeOut.Value 
                                                    : ScheduleTaskDefaultConfigs.TaskExpirationSeconds,
                                                    ScheduleTaskDefaultConfigs.TaskExpirationSeconds) - 1;
                var expiration = TimeSpan.FromSeconds(expirationInSeconds);

                //execute task with lock
                var locker = EngineContext.Current.Resolve<ILocker>();
                locker.PerformActionWithLock(ScheduleTask.Type, expiration, ExecuteTask);
            }
            catch (Exception exc)
            {
                var scheduleTaskService = EngineContext.Current.Resolve<IScheduleTaskService>();
                var storeContext = EngineContext.Current.Resolve<IStoreContext>();
                var scheduleTaskUrl = $"{storeContext.CurrentStore.Url}{ScheduleTaskDefaultConfigs.ScheduleTaskPath}";
                ScheduleTask.Status = ScheduleJobActionStatus.Error;
                ScheduleTask.LastStopTimeUtc = DateTime.UtcNow;
                scheduleTaskService.UpdateTask(ScheduleTask);
                var message = string.Format(SystemScheduleTaskMessage.ScheduleTasksErrorOnExecute, 
                                     ScheduleTask.TaskName, exc.Message, ScheduleTask.Type, scheduleTaskUrl);
                //log error
                var logger = EngineContext.Current.Resolve<ILogger>();
                var workContext = EngineContext.Current.Resolve<IWorkContext>();
                logger.Error(message, customer: workContext.CurrentCustomer); //BuildInTaskUser
                if (throwException)
                    throw;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Schedule task
        /// </summary>
        public SystemScheduleTask ScheduleTask { get; private set; }

        /// <summary>
        /// A value indicating whether the task is enabled
        /// </summary>
        public bool Enabled
        {
            get
            {
                if(!_enabled.HasValue)
                {
                    if (ScheduleTask == null) return false;
                    //以下任一情况都中止启动后台计划任务
                    // 1： 任务执行过程出错， ScheduleJobActionStatus.Error
                    // 2： 手动关毕计划任务   ScheduleJobActionStatus.Stop
                    // 则下次触发计划任务时退出任务执行
                    _enabled = (ScheduleTask.Status == ScheduleJobActionStatus.Stop
                             || ScheduleTask.Status == ScheduleJobActionStatus.Error)
                              ? false : true;
                }
                 
                return _enabled.HasValue && _enabled.Value;
                
            }
            //如果通过程序 强行赋值 Enabled=true,则为手动触发任务执行
            /** 如：
             * var task = new ProcessTask(scheduleTask) { Enabled = true };
                task.Execute(true);
             */
            set => _enabled = value;
        }

        #endregion
    }
}
